﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Inventione.Framework.BusinessLogic;
using Inventione.Framework.DataAccess;
using Inventione.Outsourcing.DataAccess;
using Inventione.Outsourcing.DataLinq;
using System.Transactions;
using Inventione.Framework.DataAccess.Exceptions;
using Inventione.Framework.BusinessLogic.Exceptions;

namespace Inventione.Outsourcing.BusinessLogic
{
    /// <summary>
    /// Classe de negócio de Ocorrência
    /// </summary>
    public class BLOcorrencia : BLBase
    {
        //Representa o objeto de acesso a dados
        private DAOcorrencia da = null;

        /// <summary>
        /// Construtor padrão
        /// </summary>
        public BLOcorrencia()
            : base("DEFAULT")
        {
            da = new DAOcorrencia("DEFAULT");
        }

        public BLOcorrencia(string tenantId)
            : base(tenantId)
        {
            this.da = new DAOcorrencia(tenantId);
        }

        /// <summary>
        /// Obtem uma ocorrência
        /// </summary>
        /// <param name="cdOcorrencia">Código da ocorrência</param>
        /// <returns>Entidade ocorrência</returns>
        public OCORRENCIA obterOcorrencia(long cdOcorrencia)
        {
            OCORRENCIA retorno = null;

            try
            {
                retorno = this.da.obterOcorrencia(cdOcorrencia);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Verifica se existe uma ocorrência registrada para o funcionário na data informada
        /// </summary>
        /// <param name="cdFuncionario">Código do funcioário</param>
        /// <param name="dtOcorrido">Data da ocorrência</param>
        /// <returns>Se há ocorrência (true / false)</returns>
        public bool existeOcorrencia(int cdFuncionario, DateTime dtOcorrido)
        {
            bool retorno = false;

            try
            {
                retorno = this.da.existeOcorrencia(cdFuncionario, dtOcorrido);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Verifica se existe uma ocorrência registrada para uma ocupação na data informada
        /// </summary>
        /// <param name="ocupacao">Entidade ocupação de funcionário</param>
        /// <param name="dtOcorrido">Data da ocorrência</param>
        /// <returns>Se há ocorrência (true / false)</returns>
        public bool existeOcorrencia(OCUPACAO ocupacao, DateTime dtOcorrido)
        {
            bool retorno = false;

            try
            {
                retorno = this.da.existeOcorrencia(ocupacao, dtOcorrido);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Valida se existe ocorrência da ocupação após uma data informada
        /// </summary>
        /// <param name="ocupacao">Ocupação de um funcionário</param>
        /// <param name="dtBase">Data base anterior as ocorrências</param>
        /// <returns>Se há ou não ocorrênica (true / false)</returns>
        public bool existeOcorrenciasAposData(OCUPACAO ocupacao, DateTime dtBase)
        {
            bool retorno = false;

            try
            {
                retorno = this.da.existeOcorrenciasAposData(ocupacao, dtBase);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }


        /// <summary>
        /// Obtem uma ocorrência de uma ocupação em uma determinada data
        /// </summary>
        /// <param name="ocupacao">Entidade ocupação</param>
        /// <param name="dtOcorrido">Data da ocorrência</param>
        /// <returns>Lista de ocorrências</returns>
        public List<OCORRENCIA> obterOcorrenciasDiaria(OCUPACAO ocupacao, DateTime dtOcorrido)
        {
            List<OCORRENCIA> retorno = null;

            try
            {
                retorno = this.da.obterOcorrenciasDiaria(ocupacao, dtOcorrido);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Obtem as ocorrência de um período, para um contrato
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="dtInicio">Data inicial do período</param>
        /// <param name="dtFim">Data final do período</param>
        /// <returns>Lista de ocorrências</returns>
        public List<OCORRENCIA> obterOcorrenciasContratoPeriodo(int cdContrato, DateTime dtInicio, DateTime dtFim)
        {
            List<OCORRENCIA> retorno = null;

            try
            {
                retorno = this.da.obterOcorrenciasContratoPeriodo(cdContrato, dtInicio, dtFim);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Obtem o total de percentual de ocorrência diária de uma ocupação
        /// </summary>
        /// <param name="ocupacao">Entidade ocupação</param>
        /// <param name="dtOcorrido">Data da ocorrência</param>
        /// <returns>Número percentual total</returns>
        public decimal obterTotalPercentualOcorrenciasDiaria(OCUPACAO ocupacao, DateTime dtOcorrido)
        {
            decimal retorno = 0;

            try
            {
                retorno = this.da.obterTotalPercentualOcorrenciasDiaria(ocupacao, dtOcorrido);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Registra um ocorrencia de funcionário
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="entity">Entidade ocorrência</param>
        public void registrarOcorrencia(int cdContrato, OCORRENCIA eOcorrencia, bool cancelarMedicoesNoPeriodoDaOcorrencia)
        {
            try
            {
                /* 
                 * Valida ocupação do funcionário na data da ocorrência, não permitindo prosseguir
                 * caso não tenha ocupação
                 */
                BLOcupacao blOcupacao = new BLOcupacao(this.TenantId);
                OCUPACAO eOcupacao = blOcupacao.obterOcupacaoFuncionario(eOcorrencia.FUNC_CD_FUNCIONARIO, eOcorrencia.OCOR_DT_OCORRIDO);

                if (eOcupacao == null)
                {
                    throw new BLException("Funcionário não ocupado na data/período informado.");
                }

                BLMedicao blMedicao = new BLMedicao(this.TenantId);
                
                bool existeMedicaoNaoAprovadaNaData = false;

                /*
                 * Valida se há medição Aprovada no período da ocorrência, não permitindo prosseguir caso tenha medição
                 */
                if (blMedicao.existeMedicaoNaData(eOcupacao.LOCO_CD_CONTRATO, eOcorrencia.OCOR_DT_OCORRIDO, "A", true))
                {
                    throw new BLException("Não é possível registrar ocorrência quando há uma medição aprovada no período.");
                }
                else
                {
                    existeMedicaoNaoAprovadaNaData = blMedicao.existeMedicaoNaData(cdContrato, eOcorrencia.OCOR_DT_OCORRIDO, "C", false);

                    if (existeMedicaoNaoAprovadaNaData && !cancelarMedicoesNoPeriodoDaOcorrencia)
                    {
                        throw new BLException(@"Não é possível registrar a ocorrência quando há medição que compreende o período.
                                    Para continuar é necessário cancelar a(s) medição(ões) existente(s).");
                    }
                }
                
                /*
                 * Lista as possíveis ocorrências diárias de uma ocupação e caso exista alguma valida se o somatório do percentual ultrapassa 100
                 */
                decimal nrTotalPercentual = this.obterTotalPercentualOcorrenciasDiaria(eOcupacao, eOcorrencia.OCOR_DT_OCORRIDO);
                if (nrTotalPercentual + eOcorrencia.OCOR_NR_PERCENTUAL_DIARIO > 100)
                {
                    throw new BLException(@"Já existe uma ocorrência registrada na data informada. <br />
                            Para registrar outra o somatório do percentual diário não pode ultrapassar 100% (cem por cento).");
                }

                // Dados da ocupação
                eOcorrencia.CARG_CD_CARGO = eOcupacao.CARG_CD_CARGO;
                eOcorrencia.FUNC_CD_FUNCIONARIO = eOcupacao.FUNC_CD_FUNCIONARIO;
                eOcorrencia.OCUP_DT_OCUPACAO = eOcupacao.OCUP_DT_OCUPACAO;

                using (TransactionScope scope = new TransactionScope())
                {
                    try
                    {
                        // Registra a ocorrência 
                        this.da.registrarOcorrencia(eOcorrencia);

                        // Cancela as medições de uma data
                        if (existeMedicaoNaoAprovadaNaData && cancelarMedicoesNoPeriodoDaOcorrencia)
                        {
                            blMedicao.cancelarMedicoesNaData(cdContrato, eOcorrencia.OCOR_DT_OCORRIDO, 1, eOcorrencia.OCOR_NM_AUTOR);
                        }

                        scope.Complete();
                    }
                    catch (Exception)
                    {
                        scope.Dispose();

                        throw;
                    }
                }
            }
            catch (BLException blex)
            {
                throw blex;
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }
        }

        /// <summary>
        /// Atualiza um ocorrencia de funcionário
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="entity">Entidade ocorrência</param>
        public void atualizarOcorrencia(int cdContrato, OCORRENCIA entity)
        {
            try
            {
                /* 
                 * Obtem e valida a ocupação de funcionário utilizada para a ocorrencia
                 */
                BLOcupacao blOcupacao = new BLOcupacao(this.TenantId);
                OCUPACAO eOcupacao = blOcupacao.obterOcupacao(entity.CARG_CD_CARGO, entity.FUNC_CD_FUNCIONARIO, entity.OCUP_DT_OCUPACAO);

                if (eOcupacao == null)
                {
                    throw new BLException("Não foi possível obter a ocupação do funcionário.");
                }

                BLMedicao blMedicao = new BLMedicao(this.TenantId);

                /*
                 * Valida se há medição Aprovada no período da ocorrência, não permitindo prosseguir caso tenha medição
                 */
                if (blMedicao.existeMedicaoNaData(eOcupacao.LOCO_CD_CONTRATO, entity.OCOR_DT_OCORRIDO, "A", true))
                {
                    throw new BLException("Não é possível atualizar uma ocorrência quando há uma medição válida no período.");
                }

                /*
                 * Lista as possíveis ocorrências diárias de uma ocupação e caso exista alguma valida se o somatório do 
                 * percentual ultrapassa 100
                 */
                decimal nrTotalPercentual = this.obterTotalPercentualOcorrenciasDiaria(eOcupacao, entity.OCOR_DT_OCORRIDO);
                if (nrTotalPercentual + entity.OCOR_NR_PERCENTUAL_DIARIO > 100)
                {
                    throw new BLException(@"Já existe uma ocorrência registrada na data informada. <br />
                            Para registrar outra o somatório do percentual diário não pode ultrapassar 100% (cem por cento).");
                }

                using (TransactionScope scope = new TransactionScope())
                {
                    try
                    {
                        /* 
                         * Atualiza a ocorrência 
                         */
                        this.da.atualizarOcorrencia(entity);

                        /*
                         * Obtem e cancela as medições
                         */
                        List<MEDICAO> medicoes = blMedicao.obterMedicoesNaData(cdContrato, entity.OCOR_DT_OCORRIDO);
                        foreach (MEDICAO item in medicoes)
                        {
                            blMedicao.cancelarMedicao(item.MEDI_CD_MEDICAO, entity.OCOR_NM_AUTOR, 1);
                        }

                        scope.Complete();
                    }
                    catch (Exception)
                    {
                        scope.Dispose();

                        throw;
                    }
                }
            }
            catch (BLException blex)
            {
                throw blex;
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }
        }


        /// <summary>
        /// Consulta ocorrências
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="cdCargo">Código do cargo</param>
        /// <param name="cdFuncionario">Código do funcionário</param>
        /// <param name="cdTipoOcorrencia">Código do tipo de ocorrência</param>
        /// <param name="nmAutor">Nome do autor</param>
        /// <param name="maximumRows">Quantidade máxima de linhas de retorno</param>
        /// <param name="startRowIndex">Linha inicial de retorno</param>
        /// <param name="sortExpression">Compo para ordenação</param>
        /// <returns>Lista de ocorrências</returns>
        public List<OCORRENCIA> consultarOcorrencias(int cdContrato, DateTime? dtInicio, DateTime? dtFinal, int cdCargo, int cdFuncionario, int cdTipoOcorrencia, string nmAutor,
                int maximumRows, int startRowIndex, string sortExpression)
        {
            List<OCORRENCIA> retorno = null;

            try
            {
                retorno = da.consultarOcorrencias(cdContrato, dtInicio, dtFinal, cdCargo, cdFuncionario, cdTipoOcorrencia, nmAutor, maximumRows, startRowIndex, sortExpression);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Consulta ocorrências
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="cdCargo">Código do cargo</param>
        /// <param name="cdFuncionario">Código do funcionário</param>
        /// <param name="cdTipoOcorrencia">Código do tipo de ocorrência</param>
        /// <param name="nmAutor">Nome do autor</param>
        /// <returns>Quantidade de registros afetados pela consulta</returns>
        public int consultarOcorrenciasCount(int cdContrato, DateTime? dtInicio, DateTime? dtFinal, int cdCargo, int cdFuncionario, int cdTipoOcorrencia, string nmAutor)
        {
            int retorno = 0;

            try
            {
                retorno = da.consultarOcorrenciasCount(cdContrato, dtInicio, dtFinal, cdCargo, cdFuncionario, cdTipoOcorrencia, nmAutor);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Valida se existe ocorrências de uma ocupação, a partir de uma data, que possui medição válida
        /// </summary>
        /// <param name="ocupacao">Entidade ocupação</param>
        /// <param name="dtBase">Data base para consulta de ocorrências após</param>
        /// <returns>Indica se possui ocorrencia com medição</returns>
        public bool validarOcorrenciasAposDataPossuiMedicaoValida(OCUPACAO eOcupacao, DateTime dtBase)
        {
            bool retorno = false;

            try
            {
                retorno = da.validarOcorrenciasAposDataPossuiMedicaoAprovada(eOcupacao, dtBase);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Exclui as ocorrência da ocupação após uma data informada
        /// </summary>
        /// <param name="ocupacao">Ocupação de um funcionário</param>
        /// <param name="dtBase">Data base para excluir as ocorrências</param>
        public void excluirOcorrenciasAposData(OCUPACAO ocupacao, DateTime dtBase)
        {
            try
            {
                // Valida se uma ocupação possui ocorrências após uma data que estão envolvidas em medição
                if (this.validarOcorrenciasAposDataPossuiMedicaoValida(ocupacao, dtBase))
                {
                    throw new BLException("A ocupação possui ocorrências já vinculadas a uma medição válida.");
                }

                // Exclui ocorrências de uma ocupação após uma data
                da.excluirOcorrenciasAposData(ocupacao, dtBase);
            }
            catch (BLException blex)
            {
                throw blex;
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }
        }

        /// <summary>
        /// Cancela (exclui) uma ocorrência
        /// </summary>
        /// <param name="entity">Entidade ocorrência</param>
        public void cancelarOcorrencia(OCORRENCIA entity)
        {
            try
            {
                /* 
                 * Obtem e valida a ocupação de funcionário utilizada para a ocorrencia
                 */
                BLOcupacao blOcupacao = new BLOcupacao(this.TenantId);
                OCUPACAO eOcupacao = blOcupacao.obterOcupacao(entity.CARG_CD_CARGO, entity.FUNC_CD_FUNCIONARIO, entity.OCUP_DT_OCUPACAO);

                if (eOcupacao == null)
                {
                    throw new BLException("Não foi possível obter a ocupação do funcionário.");
                }

                BLMedicao blMedicao = new BLMedicao(this.TenantId);

                /*
                 * Valida se há medição válida no período da ocorrência, ou seja, se existe medição que não seja cancelada,
                 * não permitindo prosseguir caso tenha medição
                 */
                if (blMedicao.existeMedicaoNaData(eOcupacao.LOCO_CD_CONTRATO, entity.OCOR_DT_OCORRIDO, "C", false))
                {
                    throw new BLException("Não é possível excluir uma ocorrência quando há uma medição válida no período.");
                }

                /*
                 * Exclui a ocorrência
                 */
                da.excluirOcorrencia(entity);
            }
            catch (BLException blex)
            {
                throw blex;
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }
        }

    }
}
